package edu.siu.math.egut.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/**
 * The class LiE exists to facilitate interaction between egut 
 * package applications and LiE processes.  
 * 
 * (LiE here refers to the software package developed by Arjeh M. Cohen, Marc van Leeuwen, and Bert Lisser.
 * More information regarding LiE may be found at http://www-math.univ-poitiers.fr/~maavl/LiE/)
 * 
 * An object in the class LiE is basically a LiE process.  
 * 
 * @author Joseph Hundley
 *
 */
public class LiE {
    private static final String BLOCKER = "IgnoreThisStringPlease";
    private static final String DEFAULT_STOP_MESSAGE = "StopMessage";
    /*An object of class LiE has three members:  a process, a BufferedReader, and a BufferedWriter.
     * Typically the Reader will be hooked up to the process's input stream, and the  writer to its output stream.
     */
    private final Process proc;
    private final BufferedReader out;
    private final BufferedWriter in;
    
    
    
    /**
     * Execute a command in LiE. 
     * 
     * For example, if lie is an object of class, LiE, then 
     * 
     * lie.exec("diagram(E8)", SHOW) 
     * 
     * causes the diagram of E8 to be printed to the screen in the same format as 
     * if one typed
     * 
     * diagram(E8) 
     * 
     * into LiE directly.
     * 
     * String s = lie.exec("unipRad(1,E8)", HIDE) 
     * 
     * initializes a String s to equal to LiE output produced by the command
     * 
     * unipRad(1,E8)
     * 
     * i.e., a list of the positive roots of E8 corresponding to the unipotent
     * radical of a certain parabolic.  
     * (the command unipRad is defined in the EGUT LiE initfile.) 
     * 
     * String s = lie.exec("unipRad(1,E8)", SHOW)
     * 
     * prints that list of roots to the screen, AND assigns its (String) value to s.
     * 
     * Finally, 
     * 
     * lie.exec("setdefault E8", ERR) 
     * 
     * reflects the fact that the LiE command "setdefault E8" will produce output only 
     * if something has gone wrong.  LiE.exec() calls System.exit(1) if 
     * that occurs.  
     * 
     * LiE.exec() also calls System.exit(1) if the output preference is HIDE and the 
     * output indicates that the input created parsing errors in LiE.  
     * 
     * TODO find some LiE input which provokes a segmentation fault, and see what happens
     * if one gives it to LiE.exec().
     * 
     * @param command input to be passed on to LiE
     * @param op output preference: SHOW, HIDE, or ERR
     * @return the output from LiE as a String.
     * @throws BadLiECommandException if command causes LiE to enter "command prolongation"
     * @throws IOException if something else goes wrong during the interaction with the LiE process.
     */
    public String exec( String command , OutputPref op) throws IOException, BadLiECommandException{
	
	in.write(command);
	in.newLine();
	/* to allow ourselves to tell when LiE's response to our command is over, 
	 * we ask it to print an explicit signal.  This is the default stop message.  Unfortunately,
	 * it is necessary to deal with a couple of contingencies under which some error might
	 * cause the default stop message not to be printed.  If the input to LiE is not complete
	 * or is not received properly, LiE may enter a state of "command prolongation."  When one
	 * is interacting with the LiE interpreter directly, this simply means that LiE prints "\"
	 * and awaits further input.  This is very useful for human users.  However, when LiE.exec()
	 * is interacting with LiE directly, "command prolongation" is something to be avoided.  We 
	 * wish that whenever the command is not received properly, LiE produce an explicit error 
	 * message.  To accomplish this goal, we exploit the fact that "print(a)print(b)" is 
	 * not valid LiE syntax.
	 */
	in.write("print(\""+ BLOCKER +"\")");
	in.newLine();
	in.write("print(\""+ BLOCKER +"\")");
	in.newLine();
	/* The function of these two print commands is to ensure that the stop message is received 
	 * correctly.  These commands may be thought of as analogous to lead blockers on a running 
	 * play in (American) football.  
	 * 
	 * If the command was incomplete in some way, and LiE is awaiting further instructions, the 
	 * first BLOCKER command will probably cause a syntax error.  If it does not, then the 
	 * second will. This ensures that LiE will exit "command prolongation," if it was, indeed, 
	 * in it.
	 * 
	 * The command to print the default stop message then follows.
	 */
	in.write("print(\""+DEFAULT_STOP_MESSAGE+"\")");
	in.newLine();
	in.flush();
	
	
	StringBuffer sb = new StringBuffer();
	String line = out.readLine();
	int blockerCount = 0;
	while( !line.trim().matches(DEFAULT_STOP_MESSAGE)){
	    
	    /* One or both of the blocker commands might 
	     * actually result in LiE output, which should be ignored.
	     * 
	     * Further, if either or both blocker command does NOT result 
	     * in LiE output, it indicates that the original command was 
	     * not read correctly. 
	     */
	    
	    // keep track of how many blocker commands produced output.
	     
	    
	    
	    if (!line.trim().matches(BLOCKER)) {
		sb.append(line.trim());
		if (!op.equals(OutputPref.HIDE))
		    System.out.println(line);
		/*
		 * If the output preference is SHOW, then we want the user to see the output. If
		 * it's ERR, then the "output" is probably an error message,
		 * which we display before exiting. (Below.)
		 */
	    }
	    else 
		blockerCount ++;
	    
	    line = out.readLine();
		
		
	}
	String result = sb.toString();
	
	if(blockerCount < 2)
	    throw new BadLiECommandException(command);
	
	/* In addition, if output preference is ERR, then any output can only be 
	 * an error message, and we call System.exit().
	 */
	if( op == OutputPref.ERR&& !result.matches(""))
	    System.exit(1);
	return result;
	    
	 
	
	
	
	
    }

 
    /**
     * Constructor.
     * @param proc LiE process
     * @param out BufferedWriter for output from LiE
     * @param in BufferedReader for input to LiE
     */
    public LiE(Process proc, BufferedReader out, BufferedWriter in) {
	super();
	this.proc = proc;
	this.out = out;
	this.in = in;
    }
    /**
     * Default Constructor.
     * 
     */
    public LiE() {
	
	proc = getNewLiEProcess(true);
		
		
		out = new BufferedReader(
			    new InputStreamReader(proc.getInputStream()));
		in =  new BufferedWriter(new OutputStreamWriter(proc
			    .getOutputStream()));
	    

    
    }


	/**
	 * Starts a new instance of LiE and returns its process.
	 * 
	 * @param redirect specifies if stderr should be 
	 * redirected to stdout for the
	 * LiE process 
	 * 
	 * @return a new process that is running LiE
	 */
	static public Process getNewLiEProcess(boolean redirect){
	    ProcessBuilder proc = new ProcessBuilder("lie");
	    proc.redirectErrorStream(redirect);
	    try {
		return proc.start();
	    } catch (IOException e) {


		e.printStackTrace();
		return null;
	    }
	}
    
}
